﻿namespace DimensionsHelper.LanguageService.Protocol;

/// <summary>
///     Position in a text document expressed as zero-based line and character
///     offset.Prior to 3.17 the offsets were always based on a UTF-16 string
///     representation. So a string of the form `a𐐀b` the character offset of the
///     character `a` is 0, the character offset of `𐐀` is 1 and the character
///     offset of b is 3 since `𐐀` is represented using two code units in UTF-16.
///     Since 3.17 clients and servers can agree on a different string encoding
///     representation(e.g.UTF-8). The client announces it's supported encoding
///     via the client capability[`general.positionEncodings`] (#clientCapabilities).
///     The value is an array of position encodings the client supports, with
///     decreasing preference(e.g.the encoding at index `0` is the most preferred
///     one). To stay backwards compatible the only mandatory encoding is UTF-16
///     represented via the string `utf-16`. The server can pick one of the
///     encodings offered by the client and signals that encoding back to the
///     client via the initialize result's property
///     `capabilities.positionEncoding`] (#serverCapabilities). If the string value
///     `utf-16` is missing from the client's capability `general.positionEncodings`
///     servers can safely assume that the client supports UTF-16. If the server
///     omits the position encoding in its initialize result the encoding defaults
///     to the string value `utf-16`. Implementation considerations: since the
///     conversion from one encoding into another requires the content of the
///     file / line the conversion is best done where the file is read which is
///     usually on the server side.
///     Positions are line end character agnostic. So you can not specify a position
///     that denotes `\r|\n` or `\n|` where `|` represents the character offset.
/// </summary>
public readonly record struct Position
{
    private Position(int line, int character)
    {
        Line = line;
        Character = character;
    }

    /// <summary>
    ///     Line position in a document (zero-based).
    /// </summary>
    /// <remarks>
    ///     <list type="bullet">
    ///         <item>
    ///             If a line number is greater than the number of lines in a document, it defaults back to the number of
    ///             lines in the document.
    ///         </item>
    ///         <item>If a line number is negative, it defaults to 0.</item>
    ///     </list>
    /// </remarks>
    public int Line { get; }


    /// <summary>
    ///     Character offset on a line in a document (zero-based).
    ///     The meaning of this offset is determined by the negotiated
    ///     PositionEncodingKind.
    /// </summary>
    /// <remarks>
    ///     If the character value is greater than the line length it defaults back to the
    ///     line length.
    /// </remarks>
    public int Character { get; }


    /// <summary>
    ///     Creates a new Position literal from the given line and character.
    /// </summary>
    /// <param name="line">The position's line.</param>
    /// <param name="character">The position's character.</param>
    public static Position Create(int line, int character)
    {
        return new Position(line, character);
    }
}


/// <summary>
///     A range in a text document expressed as (zero-based) start and end positions.
///     If you want to specify a range that contains a line including the line ending
///     character(s) then use an end position denoting the start of the next line.
///     For example:
///     <code>
/// {
///     start: { line: 5, character: 23 }
///     end : { line 6, character: 0 }
/// }
/// </code>
/// </summary>
public readonly record struct Range
{
    private Range(Position start, Position end)
    {
        Start = start;
        End = end;
    }


    private Range(int startLine, int startCharacter, int endLine, int endCharacter)
    {
        Start = Position.Create(startLine, startCharacter);
        End = Position.Create(endLine, endCharacter);
    }

    /// <summary>
    ///     The range's start position.
    /// </summary>
    public Position Start { get; }


    /// <summary>
    ///     The range's end position.
    /// </summary>
    public Position End { get; }


    /// <inheritdoc />
    public override string ToString()
    {
        return $"[start: ({Start.Line}, {Start.Character}), end: ({End.Line}, {End.Character})]";
    }


    /// <summary>
    ///     Create a new Range literal.
    /// </summary>
    /// <param name="start">The range's start position.</param>
    /// <param name="end">The range's end position.</param>
    public static Range Create(Position start, Position end)
    {
        return new Range(start, end);
    }


    /// <summary>
    ///     Create a new Range literal.
    /// </summary>
    /// <param name="startLine">The start line number.</param>
    /// <param name="startCharacter">The start character.</param>
    /// <param name="endLine">The end line number.</param>
    /// <param name="endCharacter">The end character.</param>
    /// <returns></returns>
    public static Range Create(int startLine, int startCharacter, int endLine, int endCharacter)
    {
        return new Range(startLine, startCharacter, endLine, endCharacter);
    }
}